﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Data;
using System.Xml.Linq;
using System.IO;
using System.Drawing;
using ProtocolBuilder.Model.Field;
using ProtocolBuilder.Model.Setting;

namespace ProtocolBuilder
{
    public partial class MainForm : Form
    {
        private const string pbFileFilter = "ProtocalBuilder File|*.pb";

        #region fields

        private static int newField = 1;
        private bool raisePacketChangedEvent = false;
        private bool raiseFieldChangedEvent = false;
        private bool hasChanged = false;
        private string saveFileName = string.Empty;
        private string targetPath = string.Empty;

        private int orderColumn;
        private bool ascending;

        private IBuildFieldSetting SelectedFieldSetting
        {
            get
            {
                return this.tvFields.SelectedNode.Tag as IBuildFieldSetting;
            }
            set
            {
                this.tvFields.SelectedNode.Tag = value;
            }
        }

        #endregion

        private int NewPacketID
        {
            get
            {
                int max = 0;
                foreach (ListViewItem item in lvPackets.Items)
                {
                    int id = (item.Tag as Packet).ID;
                    if (id > max)
                    {
                        max = id;
                    }
                }

                return max + 1;
            }
        }

        private Packet SelectedPacket
        {
            get;
            set;
        }

        public MainForm()
        {
            InitializeComponent();
            string[] commandLineArgs = Environment.GetCommandLineArgs();
            if (commandLineArgs.Length > 1)
            {
                try
                {
                    this.saveFileName = commandLineArgs[1];
                    this.LoadProtocol();
                }
                catch (Exception)
                {
                    MessageBox.Show("Invalide file type to open.", "Open File", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                }
            }
        }

        #region event handler

        private void MainForm_Load(object sender, EventArgs e)
        {
            this.InitializeDropDownList();
        }

        private void generateCodeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.saveToolStripMenuItem_Click(null, null);

            if (string.IsNullOrEmpty(targetPath))
            {
                FolderBrowserDialog dialog = new FolderBrowserDialog();
                if (dialog.ShowDialog() != DialogResult.OK)
                {
                    return;
                }

                targetPath = dialog.SelectedPath;
            }

            StorageManager.SaveAsHtml(targetPath, this.GenerateXmlDocument());
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.ChangeSaveFile())
            {
                txtProtocolName.Text = string.Empty;
                this.lvPackets.Items.Clear();
                this.hasChanged = false;
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.TrySave();
            this.Close();
        }

        private void OnPacketValueChanged(object sender, EventArgs e)
        {
            if (!this.raisePacketChangedEvent) return;

            this.hasChanged = true;
            Packet selected = this.SelectedPacket;
            selected.PacketName = txtPacketName.Text;
            selected.ID = (int)numberPacketID.Value;
            selected.Description = txtPacketDescription.Text;
            selected.PacketType = (PacketType)Enum.Parse(typeof(PacketType), cbPacketType.Text);

            selected.RemoveAllFields();
            foreach (TreeNode node in tvFields.Nodes)
            {
                selected.AddField(this.CreateFieldFromNode(node));
            }


            this.RefreshPacketList();
        }

        private void OnFieldValueChanged(object sender, EventArgs e)
        {
            if (!this.raiseFieldChangedEvent) return;

            this.hasChanged = true;

            bool needRefresh = !this.SelectedFieldSetting.FieldName.Equals(txtFieldName.Text);

            this.SelectedFieldSetting.TypeName = cbFieldType.Text;
            this.SelectedFieldSetting.Description = txtFieldDescription.Text;
            this.SelectedFieldSetting.FieldName = txtFieldName.Text;

            if (needRefresh)
            {
                this.RefreshFieldList();
            }

            this.OnPacketValueChanged(sender, e);
        }

        private void addPacketMenu_Click(object sender, EventArgs e)
        {
            Packet p = new Packet()
            {
                ID = this.NewPacketID,
                PacketName = string.Format("newPacket{0}", this.NewPacketID),
                PacketType = PacketType.Request,
                Description = string.Empty,
            };

            ListViewItem item = this.CreateListViewItemFromPacket(p);
            lvPackets.Items.Add(item);
            item.Selected = true;
            this.hasChanged = true;
        }

        private void lvPackets_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvPackets.SelectedItems.Count == 0) return;

            this.SelectedPacket = lvPackets.SelectedItems[0].Tag as Packet;
            if (this.SelectedPacket == null)
            {
                return;
            }

            this.raisePacketChangedEvent = false;

            txtPacketName.Text = this.SelectedPacket.PacketName;
            numberPacketID.Value = this.SelectedPacket.ID;
            txtPacketDescription.Text = this.SelectedPacket.Description;
            cbPacketType.Text = this.SelectedPacket.PacketType.ToString();
            tvFields.Nodes.Clear();
            foreach (AbsField field in this.SelectedPacket.GenerateFieldList())
            {
                TreeNode node = this.CreateTreeNodeFromField(field);
                tvFields.Nodes.Add(node);
            }

            this.raisePacketChangedEvent = true;
        }

        private void tvFields_AfterSelect(object sender, TreeViewEventArgs e)
        {
            IBuildFieldSetting setting = e.Node.Tag as IBuildFieldSetting;
            if (setting == null)
            {
                return;
            }

            this.raiseFieldChangedEvent = false;

            txtFieldName.Text = setting.FieldName;
            txtFieldDescription.Text = setting.Description;
            cbFieldType.Text = setting.TypeName;
            this.SelectedFieldSetting = setting;

            this.raiseFieldChangedEvent = true;
        }

        private void delPacketMenu_Click(object sender, EventArgs e)
        {
            if (lvPackets.SelectedItems.Count == 0)
            {
                MessageBox.Show("Please select a packet first.");
                return;
            }

            ListViewItem del = lvPackets.SelectedItems[0];
            int index = del.Index;
            del.Remove();

            this.hasChanged = true;

            int count = lvPackets.Items.Count;
            if (count > 0)
            {
                if (count <= index)
                {
                    index--;
                }

                lvPackets.Items[index].Selected = true;
            }

        }

        private void addFieldMenu_Click(object sender, EventArgs e)
        {
            AbsField f = this.NewField();

            this.AddAndSelect(f);
            this.OnPacketValueChanged(null, EventArgs.Empty);
        }

        private void insertFieldMenu_Click(object sender, EventArgs e)
        {
            AbsField f = this.NewField();
            this.InsertAndSelect(f);
            this.OnPacketValueChanged(null, EventArgs.Empty);
        }

        private void delFieldMenu_Click(object sender, EventArgs e)
        {
            TreeNode node = tvFields.SelectedNode;
            if (node == null)
            {
                MessageBox.Show("Please select a field first.");
                return;
            }

            node.Remove();
            this.OnPacketValueChanged(null, EventArgs.Empty);
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(saveFileName))
            {
                if (this.ChangeSaveFile())
                {
                    this.SaveProtocol();
                }
            }
            else
            {
                this.SaveProtocol();
            }
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = pbFileFilter;
            if (dialog.ShowDialog() != DialogResult.OK)
            {
                return;
            }

            saveFileName = dialog.FileName;
            this.LoadProtocol();
        }

        #endregion

        #region private function

        private bool ChangeSaveFile()
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = pbFileFilter;
            bool result = dialog.ShowDialog() == DialogResult.OK;

            if (result)
            {
                saveFileName = dialog.FileName;
            }
            return result;
        }

        private void InitializeDropDownList()
        {
            cbPacketType.Items.Clear();
            cbPacketType.Items.AddRange(Enum.GetNames(typeof(PacketType)));

            cbFieldType.Items.Clear();
            cbFieldType.Items.AddRange(FieldTypeManager.FieldTypes.ToArray());
        }

        private void AddAndSelect(AbsField f)
        {
            TreeNode selected = tvFields.SelectedNode;
            bool isChild = selected != null && (selected.Tag as IBuildFieldSetting).CanHasChildren;

            TreeNode node = this.CreateTreeNodeFromField(f);
            TreeNodeCollection collection = isChild ? selected.Nodes : tvFields.Nodes;
            collection.Add(node);

            tvFields.SelectedNode = node;
        }

        private void InsertAndSelect(AbsField f)
        {
            TreeNode selected = tvFields.SelectedNode;
            if (selected == null)
            {
                MessageBox.Show("Has not insert location.");
                return;
            }

            TreeNode node = this.CreateTreeNodeFromField(f);
            TreeNodeCollection collection = selected.Parent == null ? tvFields.Nodes : selected.Parent.Nodes;
            collection.Insert(selected.Index, node);

            tvFields.SelectedNode = node;
        }

        private void RefreshPacketList()
        {
            for (int i = 0; i < lvPackets.Items.Count; i++)
            {
                Packet p = lvPackets.Items[i].Tag as Packet;
                if (p == this.SelectedPacket)
                {
                    lvPackets.Items[i] = this.CreateListViewItemFromPacket(p);
                    return;
                }
            }
        }

        private void RefreshFieldList()
        {
            foreach (TreeNode node in tvFields.Nodes)
            {
                this.RefreshTreeNode(node);
            }
        }

        private void RefreshTreeNode(TreeNode parentnode)
        {
            parentnode.Text = (parentnode.Tag as IBuildFieldSetting).FieldName;

            foreach (TreeNode child in parentnode.Nodes)
            {
                this.RefreshTreeNode(child);
            }
        }

        private AbsField NewField()
        {
            AbsField result = FieldFactory.CreateField(FieldTypeManager.FieldTypes.First());
            result.FieldName = string.Format("newField{0}", newField++);

            return result;
        }

        private AbsField CreateFieldFromNode(TreeNode node)
        {
            AbsField result = (node.Tag as IBuildFieldSetting).BuildField();
            if (result.CanHasChildren)
            {
                result.RemoveAllChildren();
                foreach (TreeNode child in node.Nodes)
                {
                    result.AddChildField(this.CreateFieldFromNode(child));
                }
            }

            return result;
        }

        private ListViewItem CreateListViewItemFromPacket(Packet p)
        {
            ListViewItem result = new ListViewItem(this.GetArrayFromPacket(p));
            result.Tag = p;

            return result;
        }

        private TreeNode CreateTreeNodeFromField(AbsField f)
        {
            TreeNode result = new TreeNode(f.FieldName);
            IBuildFieldSetting setting = new NormalSetting(f.FieldType, f.FieldName, f.Description);
            result.Tag = setting;
            if (f.CanHasChildren)
            {
                foreach (AbsField child in f.GetChildrenField())
                {
                    result.Nodes.Add(this.CreateTreeNodeFromField(child));
                }
            }

            return result;
        }

        private string[] GetArrayFromPacket(Packet p)
        {
            return new string[] { p.ID.ToString(), p.PacketName, p.PacketType.ToString(), p.Description };
        }

        private void SaveProtocol()
        {
            XDocument document = this.GenerateXmlDocument();
            document.Save(saveFileName);
            this.hasChanged = false;
        }

        private XDocument GenerateXmlDocument()
        {
            Protocol protocol = new Protocol();
            protocol.Name = txtProtocolName.Text;
            foreach (ListViewItem item in lvPackets.Items)
            {
                protocol.AddPacket(item.Tag as Packet);
            }

            return new XDocument(protocol.XElement);
        }

        private void LoadProtocol()
        {
            lvPackets.Items.Clear();
            try
            {
                XDocument docment = XDocument.Load(saveFileName);
                Protocol protocol = Protocol.FromXml(docment.Root);
                txtProtocolName.Text = protocol.Name;

                foreach (Packet packet in protocol.GetAllPacket())
                {
                    ListViewItem item = this.CreateListViewItemFromPacket(packet);
                    lvPackets.Items.Add(item);
                    item.Selected = true;
                }
                this.hasChanged = false;
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show(string.Format("Can not find the file {0}.", saveFileName));
            }
            catch
            {
                MessageBox.Show("Format error.");
            }
        }

        private void TrySave()
        {
            if (hasChanged && MessageBox.Show("Protocol has been changed,save it now?", "note", MessageBoxButtons.YesNo) == DialogResult.Yes)
            {
                this.saveToolStripMenuItem_Click(null, null);
            }
        }

        private void lvPackets_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            if (e.Column != this.orderColumn)
            {
                this.ascending = true;
                this.orderColumn = e.Column;
            }
            else
            {
                this.ascending = !this.ascending;
            }

            int packetCount = lvPackets.Items.Count;
            for (int i = 0; i < packetCount; i++)
            {
                //从i开始找出最大或最小的，并插入到i位置，直接选择排序
                int temp = i;
                for (int j = i + 1; j < packetCount; j++)
                {
                    Packet tempPacket = lvPackets.Items[temp].Tag as Packet;
                    Packet current = lvPackets.Items[j].Tag as Packet;

                    switch (this.orderColumn)
                    {
                        case 0:
                            if (tempPacket.ID > current.ID == this.ascending)
                            {
                                temp = j;
                            }
                            break;
                        case 1:
                            if (string.Compare(tempPacket.PacketName, current.PacketName) > 0 == this.ascending)
                            {
                                temp = j;
                            }
                            break;
                        case 2:
                            if (tempPacket.PacketType > current.PacketType == this.ascending)
                            {
                                temp = j;
                            }
                            break;
                        case 3:
                            if (string.Compare(tempPacket.Description, current.Description) > 0 == this.ascending)
                            {
                                temp = j;
                            }
                            break;
                    }
                }

                if (temp != i)
                {
                    ListViewItem lvi = lvPackets.Items[temp];
                    lvi.Remove();
                    lvPackets.Items.Insert(i, lvi);
                }
            }
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.TrySave();
        }

        #endregion

        #region treeview drag & drog

        private void tvFields_ItemDrag(object sender, ItemDragEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.DoDragDrop(e.Item, DragDropEffects.Move);
            }
        }

        private void tvFields_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(TreeNode)))
            {
                e.Effect = DragDropEffects.Move;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void tvFields_DragDrop(object sender, DragEventArgs e)
        {
            //获得拖放中的节点
            TreeNode moveNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
            //根据鼠标坐标确定要移动到的目标节点
            Point pt = tvFields.PointToClient(new Point(e.X, e.Y));
            TreeNode targeNode = this.tvFields.GetNodeAt(pt);

            // 目标节点为空时，停止操作
            if (targeNode == null) return;

            // 防止拖放到自己的子级当中
            if (!CanMoveTo(targeNode, moveNode)) return;

            moveNode.Remove();
            TreeNodeCollection collection = targeNode.Parent == null ? tvFields.Nodes : targeNode.Parent.Nodes;
            collection.Insert(targeNode.Index, moveNode);
            this.OnPacketValueChanged(null, null);
            //tvFields.SelectedNode = moveNode;
        }

        private bool CanMoveTo(TreeNode targeNode, TreeNode moveNode)
        {
            // 去除本身拖放的情况
            if (targeNode == moveNode) return false;

            foreach (TreeNode node in moveNode.Nodes)
            {
                if (node == targeNode)
                    return false;
                if (node.Nodes.Count > 0)
                    if (CanMoveTo(targeNode, node))
                        return false;
            }

            return true;
        }

        #endregion
    }
}